package com.medusa.aps.business.common.security.server.provider;

import cn.hutool.core.util.StrUtil;
import com.medusa.aps.business.common.security.model.bean.CacheToken;
import com.medusa.aps.business.common.security.model.bean.DecodeToken;
import com.medusa.aps.business.common.security.model.bean.SecureUser;
import com.medusa.aps.business.common.security.model.enums.SecureCodes;
import com.medusa.aps.business.common.security.model.enums.TokenState;
import com.medusa.aps.business.common.security.model.enums.TokenType;
import com.medusa.aps.business.common.security.resource.helper.ISecurity;
import com.medusa.aps.business.common.security.resource.tool.jwt.JwtDecoder;
import com.medusa.aps.business.common.security.server.annotation.GrantType;
import com.medusa.aps.business.common.security.server.model.RefreshTokenRequest;
import io.vavr.control.Option;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
import com.medusa.aps.business.common.security.resource.exception.SecurityException;

/**
 * @author 张治保
 * @since 2023/11/3
 */
@Component
@GrantType("refresh_token")
@RequiredArgsConstructor
public class RefreshTokenAuthenticationProvider implements IAuthenticationProvider<RefreshTokenRequest> {

    private final JwtDecoder jwtDecoder;
    private final IReloadUserProvider refreshTokenUserProvider;

    @Override
    public SecureUser<?> authenticate(RefreshTokenRequest authentication) throws AuthenticationException {
        String token = authentication.getValue();
        if (StrUtil.isEmpty(token)) {
            throw SecurityException.of(SecureCodes.REQUEST_INVALID);
        }
        DecodeToken decode = jwtDecoder.decode(TokenType.RT, token);
        if (decode.isExpired()) {
            throw SecurityException.of(SecureCodes.REFRESH_TOKEN_EXPIRED);
        }
        SecureUser<?> secureUser = decode.getSecureUser();
        //比较 token Id 判断当前 refreshToken是否仍然可用
        Option<CacheToken> cacheTokenOpt = ISecurity.getCacheToken(secureUser.getClientType(), secureUser.getShopId(), secureUser.getId());
        //如果缓存的数据 说明 1. token 已失效 2. token 已被踢出
        //根据用户 id 重新 load 客户数据
        if (cacheTokenOpt.isEmpty()) {
            throw SecurityException.of(SecureCodes.REFRESH_TOKEN_EXPIRED);
        }
        CacheToken cacheToken = cacheTokenOpt.get();
        TokenState state = cacheToken.getState();
        boolean isSameToken = StrUtil.equals(decode.getTokenId(), String.valueOf(cacheToken.getTokenId()));
        if (!isSameToken) {
            throw SecurityException.of(SecureCodes.TOKEN_CHANGED);
        }
        return switch (state) {
            case KICK -> {
                ISecurity.offlineUsers(secureUser.getClientType(), secureUser.getShopId(), secureUser.getId());
                String message = cacheToken.getMessage();
                throw SecurityException.of(
                        StrUtil.isEmpty(message) ? SecureCodes.ACCOUNT_EXPIRED : (SecureCodes.ACCOUNT_EXPIRED.msgEx(message)));
            }
            case REFRESH -> refreshTokenUserProvider.loadUser(secureUser);
            default -> secureUser;
        };
    }

    @Override
    public Class<RefreshTokenRequest> requestType() {
        return RefreshTokenRequest.class;
    }
}
